package newcrunch.typography;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.antlr.runtime.Token;

/**
 * A convenient class to store the result of parsing
 * the source code.
 * 
 * @author WL
 */
public class TypoResults extends HashMap<String, SpaceProperty>
{
    private List<KeyRecord> constantNames;
    private List<KeyRecord> variableNames;
    private List<KeyRecord> methodNames;
    private List<KeyRecord> classInterfaceNames;
    private HashMap<String, TokenPosition> methodLength = new HashMap<String, TokenPosition>();
    private HashMap<String, TokenPosition> classLength = new HashMap<String, TokenPosition>();
    private List<Integer> stmtLines;
    private List<BraceInfo> braces;
    private List<TokenTracker> tokenRanges;
    private List<TokenPosWithLine> constructors;
    
    public enum BraceStyle { KNR, EXTENDED_BRACE, SINGLE_LINE, UNKNOWN }
    
    public enum RangeType 
    {  
        HEADER_START, HEADER_END,
        TYPE_DECL_START, TYPE_DECL_END, 
        METHOD_START, METHOD_END, 
        COMMENT_START, COMMENT_END,
        FIELD_START, FIELD_END, 
        BLANK  
    };
    
    public static class TokenTracker
    {
        public RangeType rangeType;
        public int pos;
        public int startLine;
        
        public TokenTracker(int line, int pos, RangeType type)
        {
            startLine = line;
            rangeType = type;
            this.pos = pos;
        }
    }
    
    public class BraceInfo
    {
        public BraceStyle braceStyle;
        public TokenPosWithLine tokPosWithLine;
        
        public BraceInfo(TokenPosWithLine tokPosWithLine, BraceStyle style)
        {
            this.tokPosWithLine = tokPosWithLine;
            braceStyle = style;
        }
    }
    
    public TypoResults()
    {
        super();
        
        constantNames = new ArrayList<KeyRecord>();
        variableNames = new ArrayList<KeyRecord>();
        methodNames = new ArrayList<KeyRecord>();
        classInterfaceNames = new ArrayList<KeyRecord>();
        stmtLines = new ArrayList<Integer>();
        braces = new ArrayList<BraceInfo>();
        tokenRanges = new ArrayList<TokenTracker>();
        constructors = new ArrayList<TokenPosWithLine>();
    }
    
    /**
     * Add a constructor's information.
     * @param startPos
     * @param endPos
     * @param startLine
     * @param endLine 
     */
    public void addConstructor(int startPos, int endPos, int startLine, int endLine)
    {
        constructors.add(new TokenPosWithLine(startPos, endPos, startLine, endLine));
    }
    
    /**
     * Gets all the constructors detected in the source code.
     * @return 
     */
    public List<TokenPosWithLine> getConstructors()
    {
        return constructors;
    }
    
    /**
     * Adds the range type and the start and end positions.
     * Used to record the ranges of the various token types
     * in order to track the blank spaces before
     * and after them.
     * 
     * @param type
     * @param start
     * @param end 
     */
    public void addRange(int line, int pos, RangeType type)
    {
        tokenRanges.add(new TokenTracker(line, pos, type));
    }
    
    public List<TokenTracker> getTokenRanges()
    {
        return tokenRanges;
    }
    
    public void addBrace(Token identifier, Token openBrace, Token closeBrace, 
            int startPos, int endPos)
    {
        TokenPosWithLine tokPosWithLine = new TokenPosWithLine(startPos, endPos,
                openBrace.getLine(), closeBrace.getLine());
        
        //KNR style
        if (identifier.getLine() == openBrace.getLine() &&
                identifier.getCharPositionInLine() == closeBrace.getCharPositionInLine())
            braces.add(new BraceInfo(tokPosWithLine, BraceStyle.KNR));
        
        //extended brace style
        else if (identifier.getCharPositionInLine() == openBrace.getCharPositionInLine() &&
                closeBrace.getCharPositionInLine() == openBrace.getCharPositionInLine())
            braces.add(new BraceInfo(tokPosWithLine, BraceStyle.EXTENDED_BRACE));
        
        //type everything in a single line
        else if (identifier.getLine() == openBrace.getLine() && openBrace.getLine() ==
                closeBrace.getLine())
            braces.add(new BraceInfo(tokPosWithLine, BraceStyle.SINGLE_LINE));
        
        //unknown style
        else
            braces.add(new BraceInfo(tokPosWithLine, BraceStyle.UNKNOWN));
    }
    
    public List<BraceInfo> getBraces()
    {
        return braces;
    }
    
    public void addStmtLine(int line)
    {
        stmtLines.add(line);
    }
    
    public List<Integer> getStmtLines()
    {
        return stmtLines;
    }
    
    public void addConstantName(String name, int pos, int line, int col)
    {
        constantNames.add(new KeyRecord(name, pos, line, col));
    }
    
    public void addVariableName(String name, int pos, int line, int col)
    {
        variableNames.add(new KeyRecord(name, pos, line, col));
    }
    
    public void addMethodName(String name, int pos, int line, int col)
    {
        methodNames.add(new KeyRecord(name, pos, line, col));
    }
    
    public void addClassInterfaceName(String name, int pos, int line, int col)
    {
        classInterfaceNames.add(new KeyRecord(name, pos, line, col));
    }
    
    public List<KeyRecord> getConstantNames()
    {
        return constantNames;
    }
    
    public List<KeyRecord> getVariableNames()
    {
        return variableNames;
    }
    
    public List<KeyRecord> getMethodNames()
    {
        return methodNames;
    }
    
    public List<KeyRecord> getClassInterfaceNames()
    {
        return classInterfaceNames;
    }
    
    @Override
    public String toString()
    {
        StringBuffer result = new StringBuffer();
        for (String key : this.keySet())
        {
            SpaceProperty curr = this.get(key);
            if (curr != null && curr.isNonEmpty())
                result.append(curr + "\n");
        }
        return result.toString();
    }
    
    /**
     * Updates the spaces that appear after a keyword
     * @param keyword
     * @param pos
     * @param spaces 
     */
    public void addSpaceAfter(String keyword, int pos, int line, int column, int spaces)
    {
        SpaceProperty prop = null;
        
        if ((prop = this.get(keyword)) == null)
            this.put(keyword, prop = new SpaceProperty(keyword));
        
        prop.addSpaceAfter(pos, line, column, spaces);
    }
    
    /**
     * Updates the spaces that appear before a keyword
     * @param keyword
     * @param pos
     * @param spaces 
     */
    public void addSpaceBefore(String keyword, int pos, int line, int column, int spaces)
    {
        SpaceProperty prop = null;
        
        if ((prop = this.get(keyword)) == null)
            this.put(keyword, prop = new SpaceProperty(keyword));
        
        prop.addSpaceBefore(pos, line, column, spaces);
    }
    
    /**
     * Updates the newlines that appear after a keyword
     * @param keyword
     * @param pos
     * @param spaces 
     */
    public void addNewlineAfter(String keyword, int pos, int line, int column, int spaces)
    {
        SpaceProperty prop = null;
        
        if ((prop = this.get(keyword)) == null)
            this.put(keyword, prop = new SpaceProperty(keyword));
        
        prop.addNewlineAfter(pos, line, column, spaces);
    }
    
    /**
     * Updates the newlines that appear before a keyword
     * @param keyword
     * @param pos
     * @param spaces 
     */
    public void addNewlineBefore(String keyword, int pos, int line, int column, int spaces)
    {
        SpaceProperty prop = null;
        
        if ((prop = this.get(keyword)) == null)
            this.put(keyword, prop = new SpaceProperty(keyword));
        
        prop.addNewlineBefore(pos, line, column, spaces);
    }
    
    public void addClassLength(String name, int startLine, int stopLine)
    {
            classLength.put(name, new TokenPosition(startLine, stopLine));
    }

    public void addMethodLength(String name, int startLine, int stopLine)
    {
            methodLength.put(name, new TokenPosition(startLine, stopLine));
    }

    /**
     * Token Positions store the start line and the stop line of the class.
     * @return 
     */
    public HashMap<String, TokenPosition> getClassLengths()
    {
            return classLength;
    }

    public HashMap<String, TokenPosition> getMethodLengths()
    {
            return methodLength;
    }

    public double getAverageClassLength()
    {
            if (classLength.values().size() == 0)
                    return 0;

            int sum = 0;
            for (TokenPosition pos : classLength.values())
                    sum += pos.end - pos.start + 1;
            double avg = sum / (classLength.values().size() + 0.0);
            return avg;
    }

    public double getAverageMethodLength()
    {
            if (methodLength.values().size() == 0)
                    return 0;

            int sum = 0;
            for (TokenPosition pos : methodLength.values())
                    sum += pos.end - pos.start + 1;
            double avg = sum / (methodLength.values().size() + 0.0);
            return avg;
    }
}
